## @brief Database and Models for Professor, Course, User.
#
#  User model is currently defined as DesiredCourses.
#  @author Ryan Brown, Michael Schick, Eli Perkins, Fahim Karim
#  @filename db.py

import os
import datetime
import time
import pickle
import logging
from aetycoon import DerivedProperty
from google.appengine.ext import db
from google.appengine.ext.db import polymodel
from object_list_property import ObjectListProperty

class Department(db.Model):
    code = db.StringProperty()
    fullName = db.StringProperty()

## Professor model for storing name, courses, ratings, etc.
#
#  @param db.Model The database model for datastore.
class Professor(db.Model):
    def __str__(self):
        return "ID: " + str(self.key().id()) + \
            ", " + self.firstName + ". " + self.lastName + \
            ", rmp: " + str(self.rateMyProfessorOverallRating) + \
            ", myum: " + str(self.myumRatingOverall) + \
            ", weighted: " + str(self.weightedRating)

    def __repr__(self):
        return self.__str__()

    ## First name variable.
    #  @param required=True Variable is required for any Professor object.
    firstName = db.StringProperty()

    ## Last name variable
    #  @param required=True Variable is required for any Professor object.
    lastName = db.StringProperty()

    # rateMyProfessors contains a full string department name, e.g. Mathematics
    department = db.StringProperty()

    ## Courses variable. String of all classes taught by the professor.
    #  @param required=True Variable is required for any Professor object.
    courses = db.StringListProperty()

    ## RateMyProfessor.com Helpfulness variable. Helpfulness float variable scraped from RateMyProfessor.com.
    rateMyProfessorRawRatingHelpfulness = db.FloatProperty()

    ## RateMyProfessor.com Clarity variable.
    rateMyProfessorRawRatingClarity = db.FloatProperty()

    ## RateMyProfessor.com Easiness variable.
    rateMyProfessorRawRatingEasiness = db.FloatProperty()

    ## RateMyProfessor.com Number. Quantity of ratings pulled to calculate average.
    rateMyProfessorNumber = db.IntegerProperty()

    ## myUM averages from various course evaluation questions. The business school,
    # for instance, has different questions than arts and sciences.
    # each value in the list is an average across all the course evaluations for a particular
    # professor for a particular question. only the value for "Strongly Agree/Agree" is
    # used (for now).
    myumRatings = db.ListProperty(float)

    ## myUM number of courses from a particular professor.
    myumNumber = db.IntegerProperty()

    ## Last updated ratings. Based of time and date of last scrape.
    #  @param required=True Variable is required for any Professor object.
    lastUpdate = db.DateProperty()
    
    @DerivedProperty
    def weightedRating(self):
        if (self.myumRatingOverall != None) and (self.rateMyProfessorOverallRating != None):
            return ((self.myumRatingOverall/2.0) + (self.rateMyProfessorOverallRating*10.0))
        elif self.myumRatingOverall != None:
            return self.myumRatingOverall
        elif self.rateMyProfessorOverallRating != None:
            return (self.rateMyProfessorOverallRating*25.0)
        else:
            return None
    
    @DerivedProperty
    def myumRatingOverall(self):
        if len(self.myumRatings) < 7:
            return None
        
        # calculate overall rating, if A&S, else business
        if(self.myumRatings[6] == -1.0):
        	return 0.30 * self.myumRatings[0] \
        		+ 0.20 * self.myumRatings[1] \
        		+ 0.15 * self.myumRatings[2] \
        		+ 0.10 * self.myumRatings[3] \
        		+ 0.05 * self.myumRatings[4] \
        		+ 0.20 * self.myumRatings[5]
        else:
        	return 0.10 * self.myumRatings[0] \
        		+ 0.05 * self.myumRatings[1] \
        		+ 0.05 * self.myumRatings[2] \
        		+ 0.10 * self.myumRatings[3] \
        		+ 0.05 * self.myumRatings[4] \
        		+ 0.20 * self.myumRatings[5] \
        		+ 0.00 * self.myumRatings[6] \
        		+ 0.05 * self.myumRatings[7] \
        		+ 0.10 * self.myumRatings[8] \
        		+ 0.30 * self.myumRatings[9]
        		
    @DerivedProperty
    def rateMyProfessorOverallRating(self):
        if  self.rateMyProfessorRawRatingClarity == None \
            or self.rateMyProfessorRawRatingHelpfulness == None \
            or self.rateMyProfessorRawRatingClarity == None:
                return None
        
        return (0.40 * self.rateMyProfessorRawRatingEasiness) + (0.30 * self.rateMyProfessorRawRatingHelpfulness) + (0.30 * self.rateMyProfessorRawRatingClarity)
    
dayToOffsetMap = {'A': -1, 'M' : 0, 'T' : 1, 'W' : 2,'R' : 3,'F' : 4, 'S' : 5}
offsetToFullDayMap = {-1: 'All', 0: 'Mon', 1: 'Tues', 2: 'Wed', 3: 'Thur', 4: 'Fri', 5: 'Sat'}
offsetToDayMap = {-1: 'A', 0: 'M', 1: 'T', 2: 'W', 3: 'R', 4: 'F', 5: 'S'}

class TimeRange:
    def __init__(self, startTime, endTime, day):
        self.startTime = startTime
        self.endTime = endTime
        self.day = day

    def serialize(self):
        return db.Blob(pickle.dumps(self))

    @classmethod
    def deserialize(cls, value):
        return pickle.loads(str(value))

## Course model for storing course name, section, credits, etc.
#
#  @param db.Model The database model for datastore.
class Course(db.Model):
    def __str__(self):
        s = "["
        for time in self.times:
            s = s + str(time.startTime) + " " + str(time.endTime) + " " + str(time.day) + ", "
        s = s + "]"
        return self.dept.code + self.courseNum + " " + s

    def __repr__(self):
        return self.__str__()

    ## Department variable
    dept = db.ReferenceProperty(Department)

    ## Course number variable.
    # Everything following the three letter code, e.g. 120
    courseNum = db.StringProperty()

    ## Section letter variable.
    section = db.StringProperty()

    ## Type of class variable. Used for further defining just what the class satisfies. For degree purposes.
    type = db.StringListProperty(choices=["writing", "lab", "honors", []])

    ## Credits variable. Stores number of credits a class is worth.
    credits = db.IntegerProperty()

    ## Title of class variable. Complete name of class as scraped from myUM.
    title = db.StringProperty()

    ## Prerequisites variable. Stores string name of course in DPTXXX format (three latter code and number without spaces).
    # @todo for now just store the string from the UM course schedule site... later this might change to
    #               reference other Courses. how that might be done:
    #               to support pre-requisites like (CSC120 OR CSC210) AND (BIL150 OR BIL104) this will need to be stored in CNF.
    #               possibly as a list of AND'd references to a new DB model object that has a list of Course references within it
    #               that are OR'd.
    prerequisites = db.StringProperty()

    ## Description of class variable. As scraped from myUM.
    description = db.TextProperty()

    ## Notes variable.
    notes = db.StringProperty()
    
    # a list of TimeRange objects
    times = ObjectListProperty(TimeRange, indexed=False)

    ## Professor variable. Points to the professor which teaches this class. References a Professor object.
    professor = db.ReferenceProperty(Professor)

    ## Open spots variable. Indicates how many positions are left to enroll in the class.
    openSpots = db.IntegerProperty()

    ## Location variable. Indicates what building and room the class takes place in.
    location = db.StringProperty()   

class User(db.Model):
    # First name variable. Indicates the first name of the user.
    firstName = db.StringProperty()
    # Last name variable. Indicates the last name of the user.
    lastName = db.StringProperty()

    # College variable. Indicates the college of the user.
    college = db.StringProperty()
    # Major variable. Indicates the major of the user.
    major = db.StringProperty()
    # Minor variable. Indicates the minor of the user.
    minor = db.StringProperty()
    # Track variable. Indicates the track of the user.
    track = db.StringProperty()
    # User variable. Indicates the google user account name of the user.
    user = db.UserProperty()

    # Graduation year variable. Indicates the desired graduation year as specified by the user.
    graduationYear = db.DateProperty()
    # Semesters remaining variable. Indicates the desired semesters the user intends to complete their degree in.
    semestersRemaining = db.IntegerProperty()

    # a list of Courses that the user desires to take / is taking
    selectedCourses = db.ListProperty(db.Key)

    # a list of pickled TimeRange objects... done for speed
    selectedConstraints = ObjectListProperty(TimeRange, indexed=False)
    
    # the number of schedules last generated
    numSchedules = db.IntegerProperty(default=0)

class CourseSchedule(db.Model):
    def __str__(self):
        return "rmp total: " + str(self.rmpTotal) + " myum total: " + str(self.myumTotal) + " weighted total: " + str(self.weightedTotal)

    def __repr__(self):
        return self.__str__()    
    
    user = db.ReferenceProperty(User, collection_name='schedules')

    courses = db.ListProperty(db.Key)
    rmpTotal = db.FloatProperty()
    myumTotal = db.FloatProperty()
    weightedTotal = db.FloatProperty()

class Statistics(db.Model):
    numProfessors = db.IntegerProperty(default=0)
    numDepartments = db.IntegerProperty(default=0)
    numCourses = db.IntegerProperty(default=0)